1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.testing;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import com.google.common.annotations.Beta;
23 import com.google.common.annotations.VisibleForTesting;
24 import com.google.common.base.Joiner;
25 import com.google.common.base.Objects;
26 import com.google.common.base.Throwables;
27 import com.google.common.collect.ArrayListMultimap;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ListMultimap;
30 import com.google.common.collect.Lists;
31 import com.google.common.collect.MutableClassToInstanceMap;
32 import com.google.common.collect.Ordering;
33 import com.google.common.collect.Sets;
34 import com.google.common.primitives.Ints;
35 import com.google.common.reflect.Invokable;
36 import com.google.common.reflect.Parameter;
37 import com.google.common.reflect.Reflection;
38 import com.google.common.reflect.TypeToken;
39 import com.google.common.testing.NullPointerTester.Visibility;
40 import com.google.common.testing.RelationshipTester.Item;
41 import com.google.common.testing.RelationshipTester.ItemReporter;
42
43 import junit.framework.Assert;
44 import junit.framework.AssertionFailedError;
45
46 import java.io.Serializable;
47 import java.lang.reflect.Constructor;
48 import java.lang.reflect.InvocationTargetException;
49 import java.lang.reflect.Method;
50 import java.lang.reflect.Modifier;
51 import java.util.Collection;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56
57 import javax.annotation.Nullable;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 @Beta
79 public final class ClassSanityTester {
80
81 private static final Ordering<Invokable<?, ?>> BY_METHOD_NAME =
82 new Ordering<Invokable<?, ?>>() {
83 @Override public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
84 return left.getName().compareTo(right.getName());
85 }
86 };
87
88 private static final Ordering<Invokable<?, ?>> BY_PARAMETERS =
89 new Ordering<Invokable<?, ?>>() {
90 @Override public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
91 return Ordering.usingToString().compare(left.getParameters(), right.getParameters());
92 }
93 };
94
95 private static final Ordering<Invokable<?, ?>> BY_NUMBER_OF_PARAMETERS =
96 new Ordering<Invokable<?, ?>>() {
97 @Override public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
98 return Ints.compare(left.getParameters().size(), right.getParameters().size());
99 }
100 };
101
102 private final MutableClassToInstanceMap<Object> defaultValues =
103 MutableClassToInstanceMap.create();
104 private final ListMultimap<Class<?>, Object> distinctValues = ArrayListMultimap.create();
105 private final NullPointerTester nullPointerTester = new NullPointerTester();
106
107 public ClassSanityTester() {
108
109 setDefault(byte.class, (byte) 1);
110 setDefault(Byte.class, (byte) 1);
111 setDefault(short.class, (short) 1);
112 setDefault(Short.class, (short) 1);
113 setDefault(int.class, 1);
114 setDefault(Integer.class, 1);
115 setDefault(long.class, 1L);
116 setDefault(Long.class, 1L);
117 setDefault(float.class, 1F);
118 setDefault(Float.class, 1F);
119 setDefault(double.class, 1D);
120 setDefault(Double.class, 1D);
121 setDefault(Class.class, Class.class);
122 }
123
124
125
126
127
128
129 public <T> ClassSanityTester setDefault(Class<T> type, T value) {
130 nullPointerTester.setDefault(type, value);
131 defaultValues.putInstance(type, value);
132 return this;
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 @Deprecated
151 public <T> ClassSanityTester setSampleInstances(Class<T> type, Iterable<? extends T> instances) {
152 ImmutableList<? extends T> samples = ImmutableList.copyOf(instances);
153 Set<Object> uniqueValues = new HashSet<Object>();
154 for (T instance : instances) {
155 checkArgument(uniqueValues.add(instance), "Duplicate value: %s", instance);
156 }
157 distinctValues.putAll(checkNotNull(type), samples);
158 if (!samples.isEmpty()) {
159 setDefault(type, samples.get(0));
160 }
161 return this;
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 public <T> ClassSanityTester setDistinctValues(Class<T> type, T value1, T value2) {
180 checkNotNull(type);
181 checkNotNull(value1);
182 checkNotNull(value2);
183 checkArgument(!Objects.equal(value1, value2), "Duplicate value provided.");
184 distinctValues.replaceValues(type, ImmutableList.of(value1, value2));
185 setDefault(type, value1);
186 return this;
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 public void testNulls(Class<?> cls) {
214 try {
215 doTestNulls(cls, Visibility.PACKAGE);
216 } catch (Exception e) {
217 throw Throwables.propagate(e);
218 }
219 }
220
221 void doTestNulls(Class<?> cls, Visibility visibility)
222 throws ParameterNotInstantiableException, IllegalAccessException,
223 InvocationTargetException, FactoryMethodReturnsNullException {
224 if (!Modifier.isAbstract(cls.getModifiers())) {
225 nullPointerTester.testConstructors(cls, visibility);
226 }
227 nullPointerTester.testStaticMethods(cls, visibility);
228 if (hasInstanceMethodToTestNulls(cls, visibility)) {
229 Object instance = instantiate(cls);
230 if (instance != null) {
231 nullPointerTester.testInstanceMethods(instance, visibility);
232 }
233 }
234 }
235
236 private boolean hasInstanceMethodToTestNulls(Class<?> c, Visibility visibility) {
237 for (Method method : nullPointerTester.getInstanceMethodsToTest(c, visibility)) {
238 for (Parameter param : Invokable.from(method).getParameters()) {
239 if (!NullPointerTester.isPrimitiveOrNullable(param)) {
240 return true;
241 }
242 }
243 }
244 return false;
245 }
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 public void testEquals(Class<?> cls) {
297 try {
298 doTestEquals(cls);
299 } catch (Exception e) {
300 throw Throwables.propagate(e);
301 }
302 }
303
304 void doTestEquals(Class<?> cls)
305 throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
306 IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
307 if (cls.isEnum()) {
308 return;
309 }
310 List<? extends Invokable<?, ?>> factories = Lists.reverse(getFactories(TypeToken.of(cls)));
311 if (factories.isEmpty()) {
312 return;
313 }
314 int numberOfParameters = factories.get(0).getParameters().size();
315 List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
316 List<ParameterHasNoDistinctValueException> distinctValueErrors = Lists.newArrayList();
317 List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
318 List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
319
320 for (Invokable<?, ?> factory : factories) {
321 if (factory.getParameters().size() == numberOfParameters) {
322 try {
323 testEqualsUsing(factory);
324 return;
325 } catch (ParameterNotInstantiableException e) {
326 paramErrors.add(e);
327 } catch (ParameterHasNoDistinctValueException e) {
328 distinctValueErrors.add(e);
329 } catch (InvocationTargetException e) {
330 instantiationExceptions.add(e);
331 } catch (FactoryMethodReturnsNullException e) {
332 nullErrors.add(e);
333 }
334 }
335 }
336 throwFirst(paramErrors);
337 throwFirst(distinctValueErrors);
338 throwFirst(instantiationExceptions);
339 throwFirst(nullErrors);
340 }
341
342
343
344
345
346
347
348
349 @Nullable <T> T instantiate(Class<T> cls)
350 throws ParameterNotInstantiableException, IllegalAccessException,
351 InvocationTargetException, FactoryMethodReturnsNullException {
352 if (cls.isEnum()) {
353 T[] constants = cls.getEnumConstants();
354 if (constants.length > 0) {
355 return constants[0];
356 } else {
357 return null;
358 }
359 }
360 TypeToken<T> type = TypeToken.of(cls);
361 List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
362 List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
363 List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
364 for (Invokable<?, ? extends T> factory : getFactories(type)) {
365 T instance;
366 try {
367 instance = instantiate(factory);
368 } catch (ParameterNotInstantiableException e) {
369 paramErrors.add(e);
370 continue;
371 } catch (InvocationTargetException e) {
372 instantiationExceptions.add(e);
373 continue;
374 }
375 if (instance == null) {
376 nullErrors.add(new FactoryMethodReturnsNullException(factory));
377 } else {
378 return instance;
379 }
380 }
381 throwFirst(paramErrors);
382 throwFirst(instantiationExceptions);
383 throwFirst(nullErrors);
384 return null;
385 }
386
387
388
389
390
391 public FactoryMethodReturnValueTester forAllPublicStaticMethods(Class<?> cls) {
392 ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
393 for (Method method : cls.getDeclaredMethods()) {
394 Invokable<?, ?> invokable = Invokable.from(method);
395 invokable.setAccessible(true);
396 if (invokable.isPublic() && invokable.isStatic() && !invokable.isSynthetic()) {
397 builder.add(invokable);
398 }
399 }
400 return new FactoryMethodReturnValueTester(cls, builder.build(), "public static methods");
401 }
402
403
404 public final class FactoryMethodReturnValueTester {
405 private final Set<String> packagesToTest = Sets.newHashSet();
406 private final Class<?> declaringClass;
407 private final ImmutableList<Invokable<?, ?>> factories;
408 private final String factoryMethodsDescription;
409 private Class<?> returnTypeToTest = Object.class;
410
411 private FactoryMethodReturnValueTester(
412 Class<?> declaringClass,
413 ImmutableList<Invokable<?, ?>> factories,
414 String factoryMethodsDescription) {
415 this.declaringClass = declaringClass;
416 this.factories = factories;
417 this.factoryMethodsDescription = factoryMethodsDescription;
418 packagesToTest.add(Reflection.getPackageName(declaringClass));
419 }
420
421
422
423
424
425
426
427 public FactoryMethodReturnValueTester thatReturn(Class<?> returnType) {
428 this.returnTypeToTest = returnType;
429 return this;
430 }
431
432
433
434
435
436
437
438
439
440 public FactoryMethodReturnValueTester testNulls() throws Exception {
441 for (Invokable<?, ?> factory : getFactoriesToTest()) {
442 Object instance = instantiate(factory);
443 if (instance != null
444 && packagesToTest.contains(Reflection.getPackageName(instance.getClass()))) {
445 try {
446 nullPointerTester.testAllPublicInstanceMethods(instance);
447 } catch (AssertionError e) {
448 AssertionError error = new AssertionFailedError(
449 "Null check failed on return value of " + factory);
450 error.initCause(e);
451 throw error;
452 }
453 }
454 }
455 return this;
456 }
457
458
459
460
461
462
463
464
465
466
467
468 public FactoryMethodReturnValueTester testEquals() throws Exception {
469 for (Invokable<?, ?> factory : getFactoriesToTest()) {
470 try {
471 testEqualsUsing(factory);
472 } catch (FactoryMethodReturnsNullException e) {
473
474 }
475 }
476 return this;
477 }
478
479
480
481
482
483
484
485
486
487 public FactoryMethodReturnValueTester testSerializable() throws Exception {
488 for (Invokable<?, ?> factory : getFactoriesToTest()) {
489 Object instance = instantiate(factory);
490 if (instance != null) {
491 try {
492 SerializableTester.reserialize(instance);
493 } catch (RuntimeException e) {
494 AssertionError error = new AssertionFailedError(
495 "Serialization failed on return value of " + factory);
496 error.initCause(e.getCause());
497 throw error;
498 }
499 }
500 }
501 return this;
502 }
503
504
505
506
507
508
509
510
511
512 public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
513 for (Invokable<?, ?> factory : getFactoriesToTest()) {
514 try {
515 testEqualsUsing(factory);
516 } catch (FactoryMethodReturnsNullException e) {
517
518 }
519 Object instance = instantiate(factory);
520 if (instance != null) {
521 try {
522 SerializableTester.reserializeAndAssert(instance);
523 } catch (RuntimeException e) {
524 AssertionError error = new AssertionFailedError(
525 "Serialization failed on return value of " + factory);
526 error.initCause(e.getCause());
527 throw error;
528 } catch (AssertionFailedError e) {
529 AssertionError error = new AssertionFailedError(
530 "Return value of " + factory + " reserialized to an unequal value");
531 error.initCause(e);
532 throw error;
533 }
534 }
535 }
536 return this;
537 }
538
539 private ImmutableList<Invokable<?, ?>> getFactoriesToTest() {
540 ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
541 for (Invokable<?, ?> factory : factories) {
542 if (returnTypeToTest.isAssignableFrom(factory.getReturnType().getRawType())) {
543 builder.add(factory);
544 }
545 }
546 ImmutableList<Invokable<?, ?>> factoriesToTest = builder.build();
547 Assert.assertFalse("No " + factoryMethodsDescription + " that return "
548 + returnTypeToTest.getName() + " or subtype are found in "
549 + declaringClass + ".",
550 factoriesToTest.isEmpty());
551 return factoriesToTest;
552 }
553 }
554
555
556
557
558
559
560
561
562
563
564
565 @Nullable private <T> T instantiate(Invokable<?, ? extends T> factory)
566 throws ParameterNotInstantiableException, InvocationTargetException,
567 IllegalAccessException {
568 return invoke(factory, getDummyArguments(factory));
569 }
570
571 private void testEqualsUsing(final Invokable<?, ?> factory)
572
573 throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
574 IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
575 List<Parameter> params = factory.getParameters();
576 List<FreshValueGenerator> argGenerators = Lists.newArrayListWithCapacity(params.size());
577 List<Object> args = Lists.newArrayListWithCapacity(params.size());
578 for (Parameter param : params) {
579 FreshValueGenerator generator = newFreshValueGenerator();
580 argGenerators.add(generator);
581 args.add(generateDummyArg(param, generator));
582 }
583 Object instance = createInstance(factory, args);
584 List<Object> equalArgs = generateEqualFactoryArguments(factory, params, args);
585
586 final List<List<List<Object>>> argGroups = Lists.newArrayList();
587 argGroups.add(ImmutableList.of(args, equalArgs));
588 EqualsTester tester = new EqualsTester(new ItemReporter() {
589 @Override String reportItem(Item<?> item) {
590 List<Object> factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
591 return factory.getName() + "(" + Joiner.on(", ").useForNull("null").join(factoryArgs) + ")";
592 }
593 });
594 tester.addEqualityGroup(instance, createInstance(factory, equalArgs));
595 for (int i = 0; i < params.size(); i++) {
596 List<Object> newArgs = Lists.newArrayList(args);
597 Object newArg = argGenerators.get(i).generate(params.get(i).getType());
598
599 if (newArg == null || Objects.equal(args.get(i), newArg)) {
600 if (params.get(i).getType().getRawType().isEnum()) {
601 continue;
602 }
603 throw new ParameterHasNoDistinctValueException(params.get(i));
604 }
605 newArgs.set(i, newArg);
606 tester.addEqualityGroup(createInstance(factory, newArgs));
607 argGroups.add(ImmutableList.of(newArgs));
608 }
609 tester.testEquals();
610 }
611
612
613
614
615
616 private List<Object> generateEqualFactoryArguments(
617 Invokable<?, ?> factory, List<Parameter> params, List<Object> args)
618 throws ParameterNotInstantiableException, FactoryMethodReturnsNullException,
619 InvocationTargetException, IllegalAccessException {
620 List<Object> equalArgs = Lists.newArrayList(args);
621 for (int i = 0; i < args.size(); i++) {
622 Parameter param = params.get(i);
623 Object arg = args.get(i);
624
625
626 Object shouldBeEqualArg = generateDummyArg(param, newFreshValueGenerator());
627 if (arg != shouldBeEqualArg
628 && Objects.equal(arg, shouldBeEqualArg)
629 && hashCodeInsensitiveToArgReference(factory, args, i, shouldBeEqualArg)
630 && hashCodeInsensitiveToArgReference(
631 factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) {
632
633
634
635 equalArgs.set(i, shouldBeEqualArg);
636 }
637 }
638 return equalArgs;
639 }
640
641 private static boolean hashCodeInsensitiveToArgReference(
642 Invokable<?, ?> factory, List<Object> args, int i, Object alternateArg)
643 throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
644 List<Object> tentativeArgs = Lists.newArrayList(args);
645 tentativeArgs.set(i, alternateArg);
646 return createInstance(factory, tentativeArgs).hashCode()
647 == createInstance(factory, args).hashCode();
648 }
649
650
651
652 @SuppressWarnings({"unchecked", "rawtypes"})
653 private FreshValueGenerator newFreshValueGenerator() {
654 FreshValueGenerator generator = new FreshValueGenerator() {
655 @Override Object interfaceMethodCalled(Class<?> interfaceType, Method method) {
656 return getDummyValue(TypeToken.of(interfaceType).method(method).getReturnType());
657 }
658 };
659 for (Map.Entry<Class<?>, Collection<Object>> entry : distinctValues.asMap().entrySet()) {
660 generator.addSampleInstances((Class) entry.getKey(), entry.getValue());
661 }
662 return generator;
663 }
664
665 private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator)
666 throws ParameterNotInstantiableException {
667 if (param.isAnnotationPresent(Nullable.class)) {
668 return null;
669 }
670 Object arg = generator.generate(param.getType());
671 if (arg == null) {
672 throw new ParameterNotInstantiableException(param);
673 }
674 return arg;
675 }
676
677 private static <X extends Throwable> void throwFirst(List<X> exceptions) throws X {
678 if (!exceptions.isEmpty()) {
679 throw exceptions.get(0);
680 }
681 }
682
683
684 private static <T> ImmutableList<Invokable<?, ? extends T>> getFactories(TypeToken<T> type) {
685 List<Invokable<?, ? extends T>> factories = Lists.newArrayList();
686 for (Method method : type.getRawType().getDeclaredMethods()) {
687 Invokable<?, ?> invokable = type.method(method);
688 if (!invokable.isPrivate()
689 && !invokable.isSynthetic()
690 && invokable.isStatic()
691 && type.isAssignableFrom(invokable.getReturnType())) {
692 @SuppressWarnings("unchecked")
693 Invokable<?, ? extends T> factory = (Invokable<?, ? extends T>) invokable;
694 factories.add(factory);
695 }
696 }
697 if (!Modifier.isAbstract(type.getRawType().getModifiers())) {
698 for (Constructor<?> constructor : type.getRawType().getDeclaredConstructors()) {
699 Invokable<T, T> invokable = type.constructor(constructor);
700 if (!invokable.isPrivate() && !invokable.isSynthetic()) {
701 factories.add(invokable);
702 }
703 }
704 }
705 for (Invokable<?, ?> factory : factories) {
706 factory.setAccessible(true);
707 }
708
709
710
711 return BY_NUMBER_OF_PARAMETERS.compound(BY_METHOD_NAME).compound(BY_PARAMETERS)
712 .immutableSortedCopy(factories);
713 }
714
715 private List<Object> getDummyArguments(Invokable<?, ?> invokable)
716 throws ParameterNotInstantiableException {
717 List<Object> args = Lists.newArrayList();
718 for (Parameter param : invokable.getParameters()) {
719 if (param.isAnnotationPresent(Nullable.class)) {
720 args.add(null);
721 continue;
722 }
723 Object defaultValue = getDummyValue(param.getType());
724 if (defaultValue == null) {
725 throw new ParameterNotInstantiableException(param);
726 }
727 args.add(defaultValue);
728 }
729 return args;
730 }
731
732 private <T> T getDummyValue(TypeToken<T> type) {
733 Class<? super T> rawType = type.getRawType();
734 @SuppressWarnings("unchecked")
735 T defaultValue = (T) defaultValues.getInstance(rawType);
736 if (defaultValue != null) {
737 return defaultValue;
738 }
739 @SuppressWarnings("unchecked")
740 T value = (T) ArbitraryInstances.get(rawType);
741 if (value != null) {
742 return value;
743 }
744 if (rawType.isInterface()) {
745 return new SerializableDummyProxy(this).newProxy(type);
746 }
747 return null;
748 }
749
750 private static <T> T createInstance(Invokable<?, ? extends T> factory, List<?> args)
751 throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
752 T instance = invoke(factory, args);
753 if (instance == null) {
754 throw new FactoryMethodReturnsNullException(factory);
755 }
756 return instance;
757 }
758
759 @Nullable private static <T> T invoke(Invokable<?, ? extends T> factory, List<?> args)
760 throws InvocationTargetException, IllegalAccessException {
761 T returnValue = factory.invoke(null, args.toArray());
762 if (returnValue == null) {
763 Assert.assertTrue(factory + " returns null but it's not annotated with @Nullable",
764 factory.isAnnotationPresent(Nullable.class));
765 }
766 return returnValue;
767 }
768
769
770
771
772
773 @VisibleForTesting static class ParameterNotInstantiableException extends Exception {
774 public ParameterNotInstantiableException(Parameter parameter) {
775 super("Cannot determine value for parameter " + parameter
776 + " of " + parameter.getDeclaringInvokable());
777 }
778 }
779
780
781
782
783
784
785 @VisibleForTesting static class ParameterHasNoDistinctValueException extends Exception {
786 ParameterHasNoDistinctValueException(Parameter parameter) {
787 super("Cannot generate distinct value for parameter " + parameter
788 + " of " + parameter.getDeclaringInvokable());
789 }
790 }
791
792
793
794
795
796 @VisibleForTesting static class FactoryMethodReturnsNullException extends Exception {
797 public FactoryMethodReturnsNullException(Invokable<?, ?> factory) {
798 super(factory + " returns null and cannot be used to test instance methods.");
799 }
800 }
801
802 private static final class SerializableDummyProxy extends DummyProxy
803 implements Serializable {
804
805 private transient final ClassSanityTester tester;
806
807 SerializableDummyProxy(ClassSanityTester tester) {
808 this.tester = tester;
809 }
810
811 @Override <R> R dummyReturnValue(TypeToken<R> returnType) {
812 return tester.getDummyValue(returnType);
813 }
814
815 @Override public boolean equals(Object obj) {
816 return obj instanceof SerializableDummyProxy;
817 }
818
819 @Override public int hashCode() {
820 return 0;
821 }
822 }
823 }
824